<?php

namespace app\common\service;

use app\common\model\sys\SysMenu;

/**
 * 系统权限节点读取器
 * 基于广州楚才的肩膀上修改，故节点管理功能属于开源系列
 */
class NodeService
{
  const NODE_CACHE_KEY = 'kaige_node_access'; //共用的节点数据key

  /**
   * 获取授权节点
   * @cache 所有需要权限的节点
   * @return array
   */
  public static function getAuthNode()
  {
    $nodes = cache(self::NODE_CACHE_KEY);
    if (empty($nodes)) {
      $nodeModel = new SysMenu();
      $nodes = $nodeModel->where(['is_auth' => 1])->column('node', 'node');
      cache(self::NODE_CACHE_KEY, $nodes);
    }
    return $nodes;
  }

  /**
   * 获取系统代码节点
   * @param array $nodes
   * @return array
   */
  public static function get($nodes = [])
  {
    $nodeModel = new SysMenu();
    $alias = $nodeModel->column('node,is_menu,is_auth,is_login,name,menu_id,p_menu_id,menu_type,is_lock,is_sys,type,id,status', 'node');
    $want = ['backend']; //想要的模块
    $fields = ['name' => '', 'is_menu' => 0, 'is_auth' => 0, 'is_login' => 0]; // 默认添加的字段
    $appPath = app()->getRootPath() . 'app' . DIRECTORY_SEPARATOR;
    //默认app目录下
    foreach (self::getNodeTree($appPath) as $action3) {
      //模块 过滤
      foreach ($want as $str) {
        if (stripos($action3, $str) !== 0) { //查找字符串在另一字符串中第一次出现的位置，从0开始，不存在的跳出循环
          continue 2; // 跳出两层，直接到达 至第一层的foreach 中,if也算一层
        }
      }
      
      $tmp = explode('/', $action3); //节点(模块/控制器/方法)


      list($module1, $controller2) = ["{$tmp[0]}", "{$tmp[0]}/{$tmp[1]}"]; // 抽出 模块，控制器
      //合并数组，如果多个数组元素有相同的键名，则最后的元素会覆盖前面的元素
      //1、模块 数据表有的取数据表，否则新增元素
      $nodes[$module1] = array_merge(isset($alias[$module1]) ? $alias[$module1] : $fields, ['node' => $module1, 'pnode' => '']);
      //2、控制器
      $nodes[$controller2] = array_merge(isset($alias[$controller2]) ? $alias[$controller2] : $fields, ['node' => $controller2, 'pnode' => $module1]);
      //3、方法
      $nodes[$action3] = array_merge(isset($alias[$action3]) ? $alias[$action3] : $fields, ['node' => $action3, 'pnode' => $controller2]);
    }
    foreach ($nodes as &$node) {
      list($node['is_auth'], $node['is_menu'], $node['is_login']) = [
        intval($node['is_auth']),
        intval($node['is_menu']),
        empty($node['is_auth']) ? intval($node['is_login']) : 1
      ];
    }
    return $nodes;
  }

  /**
   * php截取指定两个字符之间字符串
   * @return string
   */
  private static function get_between($input, $start, $end)
  {
    $substr = substr($input, strlen($start) + strpos($input, $start), (strlen($input) - strpos($input, $end)) * (-1));
    return $substr;
  }

  /**
   * 获取节点列表--兼容控制器一级子目录。如果控制器2级以上需要再定制
   * @param string $dirPath 路径
   * @param array $nodes 额外数据
   * @return array
   */
  public static function getNodeTree($dirPath, $nodes = [])
  {
    foreach (self::scanDirFile($dirPath) as $filename) {
      $matches = [];
      $tmpfm = str_replace(DIRECTORY_SEPARATOR, '/', $filename);
      $tmpcp = self::get_between($tmpfm, 'controller', '.php');
      $tcps = explode('/', $tmpcp);
      $tmpcp = $tcps[count($tcps) - 1];
      if (!preg_match('|/(\w+)/controller/(\w+)|', $tmpfm, $matches) || count($matches) !== 3) {
        continue;
      }
      if ($matches[2] != $tmpcp) {
        array_push($matches, $tmpcp);
        $className = 'app' . str_replace('/', '\\', $matches[0]) . '\\' . $matches[3];
      } else {
        $className = 'app' . str_replace('/', '\\', $matches[0]);
      }
      if (!class_exists($className)) continue;
      foreach (get_class_methods($className) as $funcName) {
        //过滤以 _ 开头的，initialize 与 registerMiddleware 
        if (strpos($funcName, '_') !== 0 && $funcName !== 'initialize' && $funcName !== 'registerMiddleware') {
          if ($matches[2] != $tmpcp) {
            $nodes[] = self::parseNodeStr("{$matches[1]}/{$matches[2]}") .'.'. self::parseNodeStr($matches[3])  . '/' . strtolower($funcName);
          } else {
            $nodes[] = self::parseNodeStr("{$matches[1]}/{$matches[2]}") . '/' . strtolower($funcName);
          }
        }
      }
    }
    return $nodes;
  }

  /**
   * 驼峰转下划线规则
   * @param string $node
   * @return string
   */
  public static function parseNodeStr($node)
  {
    $tmp = [];
    foreach (explode('/', $node) as $name) {
      $tmp[] = strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
    }
    return trim(join('/', $tmp), '/');
  }

  /**
   * 获取所有PHP文件
   * @param string $dirPath 目录
   * @param array $data 额外数据
   * @param string $ext 有文件后缀
   * @return array
   */
  private static function scanDirFile($dirPath, $data = [], $ext = 'php')
  {
    foreach (scandir($dirPath) as $dir) {
      if (strpos($dir, '.') === 0) {
        continue;
      }
      $tmpPath = realpath($dirPath . DIRECTORY_SEPARATOR . $dir);
      if (is_dir($tmpPath)) {
        $data = array_merge($data, self::scanDirFile($tmpPath));
      } elseif (pathinfo($tmpPath, 4) === $ext) {
        $data[] = $tmpPath;
      }
    }
    return $data;
  }

  /**
   * JSON(对象)转数组
   */
  public static function object_array($data)
  {
    if (is_object($data)) {
      $data =  json_decode(json_encode($data), true);
    }
    return $data;
  }
}
